package com.stone.serverless.sdk.utils;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.TypeUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import lombok.SneakyThrows;

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Description: TODO
 * @Author: MECHREVO
 * @Date: 2021/6/10 17:21
 * @Version: 1.0.0
 */
public final class InvokeUtil {

    private InvokeUtil() {
    }

    /**
     * 默认软引用，只有内存不够才会被强制回收
     */
    private static final Map<String, TypeInfo> CALLER_INFO = new HashMap<>();


    /**
     * 对象复制
     *
     * @param source
     * @param typeInfo
     * @return
     */
    public static Object copy(Object source, TypeInfo typeInfo) throws JsonProcessingException {
        Type t1 = typeInfo.getType();
        Type t2 = typeInfo.getTypeTwo();
        String returnType = typeInfo.getReturnType();
        Object result = null;
        if (ObjectUtil.isNotNull(source)) {
            String json = null;
            //如果都是String 直接返回
            if (source instanceof String && CharSequenceUtil.equals(returnType, Object.class.getName())
                    && t1 == String.class) {
                return source;
            } else {
                json = JsonUtil.toStr(source);
            }
            result = transfer(json, typeInfo);
        }
        return result;
    }

    @SneakyThrows
    public static Object transfer(String json, TypeInfo typeInfo) {
        Type t1 = typeInfo.getType();
        Type t2 = typeInfo.getTypeTwo();
        JavaType p1 = JsonUtil.getJavaType(t1);
        String returnType = typeInfo.getReturnType();
        Object result = null;
        if (CharSequenceUtil.equals(returnType, List.class.getName())) {
            result = JsonUtil.toList(json, p1);
        } else if (CharSequenceUtil.equals(returnType, Map.class.getName())) {
            JavaType p2 = JsonUtil.getJavaType(t2);
            result = JsonUtil.toMap(json, p1, p2);
        } else {
            result = JsonUtil.toBean(json, p1);
        }
        return result;
    }

    /**
     * 获取返回值信息
     *
     * @param cl
     * @param type
     * @param cacheName
     * @return
     */
    public static TypeInfo getTypeInfo(Class<?> cl, Type type, String cacheName) {
        String prefix = "";
        ClassLoader classLoader = cl.getClassLoader();
        if (ObjectUtil.isNotNull(classLoader)) {
            prefix = classLoader.toString();
        }
        cacheName = prefix + "_" + cacheName;
        TypeInfo returnTypeInfo = CALLER_INFO.get(cacheName);
        if (ObjectUtil.isNotNull(returnTypeInfo)) {
            return returnTypeInfo;
        }
        returnTypeInfo = new TypeInfo();
        if (ClassUtil.isAssignable(List.class, cl)) {
            Type typeArgument = TypeUtil.getTypeArgument(type);
            if (ObjectUtil.isNull(typeArgument)) {
                returnTypeInfo.setType(type);
            } else {
                returnTypeInfo.setType(typeArgument);
            }
            returnTypeInfo.setReturnType(List.class.getName());
        } else if (ClassUtil.isAssignable(Map.class, cl)) {
            Type argumentOne = TypeUtil.getTypeArgument(type, 0);
            Type argumentTwo = TypeUtil.getTypeArgument(type, 1);
            returnTypeInfo.setType(argumentOne);
            returnTypeInfo.setTypeTwo(argumentTwo);
            returnTypeInfo.setReturnType(Map.class.getName());
        } else {
            returnTypeInfo.setType(type);
            returnTypeInfo.setReturnType(Object.class.getName());
        }
        CALLER_INFO.put(cacheName, returnTypeInfo);
        return returnTypeInfo;
    }

    public static class TypeInfo {
        private Type type;
        private Type typeTwo;
        private String returnType;

        public Type getType() {
            return type;
        }

        public void setType(Type type) {
            this.type = type;
        }

        public Type getTypeTwo() {
            return typeTwo;
        }

        public void setTypeTwo(Type typeTwo) {
            this.typeTwo = typeTwo;
        }

        public String getReturnType() {
            return returnType;
        }

        public void setReturnType(String returnType) {
            this.returnType = returnType;
        }
    }
}
